---------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------
-- File: fifo_buffer - fifo buffer source file.
-- Author: Andy Schneck                         
-- Description: There are four concurrent processes running: check empty, check full, 
-- write, and read: 
-- - Check empty: Checks for an empty buffer, sensative to read_en, and clk
-- - Check full: Checks for a full buffer, sensative to write_en, and clk
-- - write: sensative to clk, write_en (high), and reset (high)
-- - read: sensative to clk, read_en (high), and reset (high)	
--
-- Inputs: data_in[15:0]: This is the 16-bit data input where data_in[15:8] represents 
--         the address of the device and data_in[7:0] represents the actual data.
--         write_en: Enable flag to allow a write to the buffer to occur (active high).
--         read_en: Enable flag to allow a read from the buffer to occur (active high).
--         reset: This enable bit will flush the buffer (active high).
--         clk: Write is sensative to positive edge, read is sensative to negative edge
--
-- Outputs: full: Flag that indicates a full buffer.
--          empty: Flag that indicates an empty buffer.
--          data_out[15:0]: The 16-bit data output where data_out[15:8] represents 
--          the address of the device and data_out[7:0] represents the actual data.
---------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.ALL;
use ieee.std_logic_arith.ALL;
use ieee.std_logic_unsigned.ALL;

--------------------------------------------
-- entity declaration for the fifo buffer --	   
--------------------------------------------
entity fifo_buffer is
port(
        data_in     :   in std_logic_vector(7 downto 0);    -- input data (16 bits)
        write_en    :   in std_logic ;--:= '1';             -- write enable flag (active high)
        read_en     :   in std_logic;                       -- read enable flag (active high)
        clk         :   in std_logic;                       -- system clock 
        reset       :   in std_logic;                       -- reset (active high)
        full        :   out std_logic ;--:= '0';            -- buffer full flag
        empty       :   out std_logic;                      -- buffer empty flag
        data_out    :   out std_logic_vector(7 downto 0));  -- output data (16 bits)
end fifo_buffer;

-------------------------------------------
-- architecture body for the fifo buffer --	   
-------------------------------------------
architecture behav of fifo_buffer is
    type buff is array(integer range 0 to 63) of std_logic_vector(7 downto 0);-- two-dimensional array
    signal fifo_array   :   buff;                           -- actual 2-D array
    signal write_ptr    :   integer range 0 to 63 := 0;     -- write pointer (index)
    signal read_ptr     :   integer range 0 to 63 := 0;     -- read pointer (index)
    signal temp_full    :   std_logic := '0';               -- temp full flag
    signal temp_empty   :   std_logic := '0';               -- temp empty flag
    signal last_write   :   std_logic := '0';               -- last action flag: write
    signal last_read    :   std_logic := '0';               -- last action flag: read
	 

begin   


    ---------------------------------------------------------------------------------------
    -- Check/Set full buffer process:
    -- sets the full flag and temp full flag when the buffer is full       
    -- when write_ptr and read_ptr are equal and last action was a write.
    ---------------------------------------------------------------------------------------
    process(clk, read_en, reset)
    begin	 
		if(reset = '0') then
	        if(read_en = '1') then -- if read enable is selected, buffer will not be full
	            temp_full <= '0';
	        else -- if read enable is not selected      
	            -- if the write pointer is 63 and the last action was a write, then the buffer is full
				if(write_ptr = 63 and last_write = '1') then
	                temp_full <= '1';  
	            -- if last action is a read and buffer was full, then the buffer is not full
	            elsif ((last_read = '1') and (temp_full = '1')) then
	                temp_full <= '0';  
	            -- if last action was a read and write, buffer stays the same
	            elsif(last_write = '1' and last_read = '1') then
	                temp_full <= '0'; 
	            -- default condition
	            else
	                temp_full <= '0';
	            end if;
	        end if;
		else
			temp_full <= '0'; -- if reset is enabled, the buffer will not be full
		end if;
    end process;

    ---------------------------------------------------------------------------------------
    -- Check/Set empty buffer process:
    -- sets the empty flag and temp empty flag when the buffer is empty
    -- when write_ptr and read_ptr are equal and the last action is a read
    ---------------------------------------------------------------------------------------
    process(clk, write_en, reset)
    begin   
		if(reset = '0') then
	        if(write_en = '1') then  -- if write enable is selected, buffer will not be empty
	            temp_empty <= '0';
	        else -- if write enable is not selected     
	            -- if the read pointer is zero and the last action was a read, then the buffer is empty
				if(read_ptr = 0 and last_read = '1') then
	                temp_empty <= '1'; 
	            -- if last action was a write and buffer was empty, then the buffer is now not empty
	            elsif((last_write = '1') and (temp_empty = '1')) then
	                temp_empty <= '0';          
	            -- if last action was a write and read, buffer stays the same
	            elsif(last_write = '1' and last_read = '1') then
	                temp_empty <= '0';     
	            -- default condition
	            else
	                temp_empty <= '0';
	            end if;
	        end if;
		else
			temp_empty <= '1'; -- if reset is enabled, the buffer will be empty
		end if;
    end process;
    
    ---------------------------------------------------------------------------------------
    -- Write process:
    -- write when clock is high and when the buffer is not full
    -- full when pointers are equal and the last action is write
    -- if last_read is high, reset last_write, or 
    -- set last_write to high if there is currently a write		 
	-- if reset is high, reset all flags and flush buffer
    ---------------------------------------------------------------------------------------
    process(write_en, reset)
    begin
        if(reset = '0') then    
            if(last_read = '1') then -- if last action was a read, reset the last write flag
                last_write <= '0';
            end if;     
            
            -- if write enable flag is high and the buffer is not full, write to the appropriate index
            if(write_en = '1' and temp_full = '0') then
                fifo_array(write_ptr) <= data_in; -- write the data
                last_write <= '1'; -- set the last action flag
				
                if (write_ptr < 63)then -- keep incrementing while the write enable is selected
                    write_ptr <= write_ptr + 1;
                else
                    write_ptr <= 0; -- reset pointer
                end if; 
			else
				last_write <= '0';
            end if;	
		else  -- if reset is selected
			write_ptr <= 0; -- reset the write pointer	

			-- flush the buffer
          	for counter in 0 to 63 loop
               fifo_array(counter) <= "00000000";
           	end loop;
        end if;
    end process;          
    
    ---------------------------------------------------------------------------------------
    -- Read process 
    -- read when clock is low and when the buffer is not empty
    -- empty when pointers are equal and the last action is read
    -- if last_write is high, reset last_read, or
    -- set last_read to high if there is currently a read	   
	-- if reset is high, reset all flags and flush the buffer
    ---------------------------------------------------------------------------------------
    process(read_en, reset)
    begin
        if(reset = '0')then 
            if(last_write = '1') then -- if last action was a write, reset the last read flag
                last_read <= '0';
            end if;     
        
            -- if the read enable is high and the buffer is not empty, read from the appropriate index
            if(read_en = '1' and temp_empty = '0')then      
                data_out <= fifo_array(read_ptr); -- read the data
                last_read <= '1'; -- set last action flag  
				
                if (read_ptr < 63)then -- keep incrementing while the read enable is selected
                    read_ptr <= read_ptr + 1;           
                else
                    read_ptr <= 0; -- reset pointer
                end if;
			else
				last_read <= '0';
            end if;	
		elsif(reset = '1') then -- if reset is selected
			read_ptr <= 0; -- reset the read pointer	
        end if;
    end process;  	
    
	-- set the flags
	full <= temp_full;
	empty <= temp_empty;
end behav;
